import proto from "../../proto/proto";
import { RawData, WebSocket } from "ws";
import { authcode, checkHeartTimeout, authTImeout } from "../common/SocketConfig";
import { MsgType, Protocol, ProtocolMap } from "../../proto/msg";
import { Connection } from "./Connection";


export enum ConnectCloseReason {
    /**
     * 掉线
     */
    offline = 'offline',
    /**
     * 心跳超时
     */
    heartTimeOut = 'heartTimeOut',
    /**
     * 非法连接
     */
    UnAuthorized = 'UnAuthorized',
    /**
     * 验证超时
     */
    authTimeout = 'authTimeout',
    /**
     * 被挤号
     */
    squeezed = 'squeezed',
    /**
     * 离开
     */
    leave = 'leave',
}

/**
 * 客户端连接
 */
export class ClientConnection extends Connection {
    public userId: number = 0;

    private _isAuth: boolean = false;
    private _heartHandle: NodeJS.Timeout | null = null;
    private _authHandle: NodeJS.Timeout | null = null;
    private _closeReason: ConnectCloseReason | null = null;

    constructor(ws: WebSocket) {
        super(ws)
        //开始权限验证检查
        this._authHandle = setTimeout(() => {
            this.clear(ConnectCloseReason.authTimeout);
        }, authTImeout * 1000)
    }

    /**
     * 继承重写_onerror
     * @param e 
     */
    protected _onerror(e: Error) {
        this.clear(ConnectCloseReason.offline);
    }

    /**
     * 继承重写_onclose
     * @param code 
     * @param reason 
     */
    protected _onclose(code: number, reason: Buffer) {
        this.clear(ConnectCloseReason.offline);
    }

    /**
     * 继承重写_onmsg
     * @param data 
     * @param isBinary 
     */
    protected _onmsg(data: RawData, isBinary: boolean) {
        if (isBinary) {
            data = data as ArrayBuffer;
            this._msgParse!.parse(data, (msgType: number, unit8Array: Uint8Array) => {
                if (msgType != MsgType.SNED) {
                    console.log('非法消息类型')
                    return
                }
                // 解包
                const data = proto.ReqWrap.decode(unit8Array)
                if (!this._checkProtocol(data.cmd)) return;
                const reqWrap = proto.ReqWrap.decode(unit8Array);
                const req = this._getProtocolReq(data.cmd)
                const body = req.decode(reqWrap.body);
                if (this.checkAuth(data.cmd, body, reqWrap)) {
                    if (data.cmd == Protocol.BASE_HEART) {
                        // console.log('收到心跳');
                        this.sendData(data.cmd, reqWrap, proto.CODE.SUCCESS);
                        this.clearHeart();
                        this.startHeart();
                    }
                    else {
                        // console.log('收到消息:' + cmd)
                        if (this.onMsg) {
                            this.onMsg(this, data.cmd, reqWrap, body)
                        }
                    }
                }
            })
        }
        else {
            console.log(`收到文本消息:${data}`)
        }
    }

    /**
     * 检查权限
     * @param cmd 
     * @param data 
     * @returns 
     */
    private checkAuth(cmd: number, body: proto.Author, reqWrap: proto.ReqWrap): boolean {
        // 已验证
        if (this._isAuth) return true;
        //开始验证
        if (cmd == Protocol.BASE_AUTH) {
            const code = body.authcode
            //权限验证通过
            if (code == authcode) {
                console.log('权限验证通过');
                this._isAuth = true;
                //清楚验证超时
                this.clearAuthTimer();
                //开始心跳检查
                this.startHeart();
                //回复授权结果
                this.sendData(cmd, reqWrap, proto.CODE.SUCCESS);
                //检查通过但本次消息已使用
                return false;
            }
        }
        //非法连接
        this.clear(ConnectCloseReason.UnAuthorized);
        return false;
    }
    /**
     * 开启心跳检查
     */
    private startHeart() {
        this._heartHandle = setTimeout(() => {
            console.log('心跳超时');
            this.clear(ConnectCloseReason.heartTimeOut);
        }, checkHeartTimeout * 1000)
    }

    /**
     * 清理心跳
     */
    private clearHeart() {
        if (this._heartHandle) {
            clearTimeout(this._heartHandle)
            this._heartHandle = null
        }
    }
    /**
     * 清除验证超时
     */
    private clearAuthTimer() {
        if (this._authHandle) {
            clearTimeout(this._authHandle)
            this._authHandle = null
        }
    }

    /**
     * 关闭连接
     * @param reason 
     * @returns 
     */
    public clear(reason: ConnectCloseReason) {
        //已经关闭了
        if (this._ws == null) return;


        //清理数据
        this._isAuth = false;
        this._msgParse!.clear();
        this._msgParse = null;
        this.clearHeart();
        this.clearAuthTimer();
        if (this._ws) {
            this._ws.close();
            this._ws = null
        }
        this.onMsg = null;
        if (this.onClose)
            this.onClose(this, this._closeReason ?? reason)
        this.onClose = null;
        this._closeReason = null;
    }

    /**
     * 提前设置关闭的原因
     * @param reason 
     */
    public setReason(reason: ConnectCloseReason) {
        this._closeReason = reason;
    }

    /**
     * 检查协议
     * @param cmd 
     * @returns 
     */
    private _checkProtocol(cmd: number) {
        if (!ProtocolMap.has(cmd)) {
            console.warn(`发送的消息不存在:${cmd}`)
            return false
        }
        const protcol = ProtocolMap.get(cmd);
        const req = (proto as any)[protcol!.req];
        if (!req) {
            console.warn(`未定义接结构体:${protcol!.req}`);
            return false
        }
        const resp = (proto as any)[protcol!.resp];
        if (!resp) {
            console.warn(`未定义接结构体:${protcol!.resp}`);
            return false
        }
        return true
    }

    /**
     * 获取req
     * @param cmd 
     * @returns 
     */
    private _getProtocolReq(cmd: number) {
        return (proto as any)[ProtocolMap.get(cmd)!.req];
    }

    /**
    * 获取resp
    * @param cmd 
    * @returns 
    */
    private _getProtocolResp(cmd: number) {
        return (proto as any)[ProtocolMap.get(cmd)!.resp];
    }

    /**
     * 发送消息
     * @param cmd 
     * @param reqWrap 
     * @param code 
     * @param data 
     * @returns 
     */
    public sendData(cmd: number, reqWrap: proto.ReqWrap, code: proto.CODE, data: any = {}) {
        if (!this._checkProtocol(cmd)) return
        let msg = '未知错误';
        switch (code) {
            case proto.CODE.SUCCESS:
                msg = '成功'
                break
            case proto.CODE.PARAM_ERROR:
                msg = '参数错误'
                break
            case proto.CODE.CMD_NOT_FOUND:
                msg = '消息号不存在'
                break
        }

        const resp = this._getProtocolResp(cmd)
        const body = resp.encode(data).finish()
        const respWrap: Uint8Array = proto.RespWrap.encode({ cmd, code, seq: reqWrap.seq, msg, body }).finish()
        if (cmd != Protocol.BASE_HEART) {
            console.log(`response--cmd:${cmd},code:${code},msg:${msg},data:${JSON.stringify(data)}`)
        }
        this.send(MsgType.SNED, respWrap)
    }

    /**
     * 推送消息
     * @param cmd 
     * @param data 
     * @returns 
     */
    public pushData(cmd: number, data: any = {}) {
        if (!this._checkProtocol(cmd)) return
        const resp = this._getProtocolResp(cmd)
        const body = resp.encode(data).finish()
        const respWrap = proto.PushWrap.encode({ cmd, body }).finish()
        console.log(`push--data:${JSON.stringify(body.buffer)}`)
        console.log(`push--cmd:${cmd},data:${JSON.stringify(data)}`)
        this.send(MsgType.PUSH, respWrap)
    }
}